package br.poli.ecomp.sma.massim.arch;

import jason.JasonException;
import jason.ReceiverNotFoundException;
import jason.RevisionFailedException;
import jason.asSemantics.ActionExec;
import jason.asSemantics.Intention;
import jason.asSemantics.Message;
import jason.asSyntax.Atom;
import jason.asSyntax.Literal;
import jason.asSyntax.LiteralImpl;
import jason.asSyntax.NumberTermImpl;
import jason.asSyntax.Pred;
import jason.asSyntax.Term;
import jason.environment.grid.Location;
import jason.mas2j.ClassParameters;
import jason.runtime.Settings;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class EcompArch extends arch.IdentifyCrashed {

	protected Logger logger = Logger.getLogger(EcompArch.class.getName());

	String     simId = null;
	int	       myId  = -1;
	boolean    playing = false;

	int        simStep  = 0;

	public static Atom aFENCE 		= new Atom("fence");
	public static Atom aNOFENCE 	= new Atom("no_fence");
	public static Atom aOBSTACLE    = new Atom("obstacle");
	public static Atom aENEMY       = new Atom("enemy");
	public static Atom aENEMYCORRAL = new Atom("enemycorral");
	public static Atom aALLY        = new Atom("ally");
	public static Atom aEMPTY       = new Atom("empty");
	public static Atom aCOW			= new Atom("cow");
	public static Atom aSWITCH		= new Atom("switch");
	
    Location lo1 = new Location(-1,-1), // last locations of the agent 
             lo2 = new Location(-1,-1), 
             lo3 = new Location(-1,-1), 
             lo4 = new Location(-1,-1),
             lo5 = new Location(-1,-1),
             lo6 = new Location(-1,-1),
             lo7 = new Location(-1,-1);

	@Override
	public void initAg(String agClass,
			ClassParameters bbPars, String asSrc,
			Settings stts) throws JasonException {
		super.initAg(agClass, bbPars, asSrc, stts);

		// Obtem as configura��es do agente:
		String username = stts.getUserParameter("username");
        if (username.startsWith("\"")) username = username.substring(1,username.length()-1);
        String password = stts.getUserParameter("password");
        if (password.startsWith("\"")) password = password.substring(1,password.length()-1);
        String host = stts.getUserParameter("host");
		int port = Integer.parseInt(stts.getUserParameter("port"));

		// Inicia os Threads do agente:
		ServerProxyThread proxy = new ServerProxyThread(this, host, port, username, password);
        new AgentActionThread(proxy).start();
        proxy.start();
	}

	/*********************************************************************************************************************************/
	
	class ServerProxyThread extends Thread {

		private Logger logger = Logger.getLogger(ServerProxyThread.class.getName());

		@SuppressWarnings("serial")
		private class SocketClosedException extends Exception {}
		private int networkport;
		private String networkhost;
		private Socket socket;
		
		private InputStream inputstream;
		private OutputStream outputstream;
		
		private String username;
		private String password;
		
		private DocumentBuilderFactory documentbuilderfactory;
		private TransformerFactory transformerfactory;

	    String         rid; // the response id of the current cycle
		EcompArch arq;
		boolean        running = true;
		
		private DocumentBuilder documentbuilder;

		ConnectionMonitor monitor = new ConnectionMonitor();
		
		private boolean connected = false;
		
		public ServerProxyThread(EcompArch arq,
				String host, int port, String username, String password) {
			networkhost = "localhost";
			networkport = 0;
			
			//socket = new Socket();
			documentbuilderfactory = DocumentBuilderFactory.newInstance();
			transformerfactory = TransformerFactory.newInstance();
		
			//logger.setLevel(Level.FINE);
			
			if (host.startsWith("\"")) {
				host = host.substring(1,host.length()-1);
			}
			setPort(port);
			setHost(host);
			setUsername(username);
			setPassword(password);
			this.arq = arq;
			try {
				documentbuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
				//transformer = TransformerFactory.newInstance().newTransformer();
			} catch (Exception e) {
				e.printStackTrace();
			}
			
	        connect();
//			monitor.start();
		}
		
		public void finish() {
		    running = false;
		    monitor.interrupt();
		}
		
	    public void run() {
	        while (running) {
	            try {
	                if (isConnected()) {
	                    Document doc = receiveDocument();
	                    if (doc != null) {
	                        Element el_root = doc.getDocumentElement();
	        
	                        if (el_root != null) {
	                            if (el_root.getNodeName().equals("message")) {
	                                processMessage(el_root);
	                            } else {
	                                logger.log(Level.SEVERE,"unknown document received");
	                            }
	                        } else {
	                            logger.log(Level.SEVERE, "no document element found");
	                        }
	                    }
	                } else {
	                    // wait auto-reconnect
	                    logger.info("waiting reconnection...");
	                    try { Thread.sleep(2000); } catch (InterruptedException e1) {}
	                }
	            } catch (Exception e) {
	                logger.log(Level.SEVERE, "ACProxy run exception", e);
	            }
	        }
	    }
		
		public String getHost() {
			return networkhost;
		}
		public void setHost(String host) {
			this.networkhost = host;
		}
		
		public int getPort() {
			return networkport;
		}
		public void setPort(int port) {
			this.networkport=port;
		}
		
		public String getUsername() {
			return username;
		}
		public void setUsername(String username) {
			this.username=username;
		}
		
		public String getPassword() {
			return username;
		}
		public void setPassword(String password) {
			this.password=password;
		}
		
		public void sendAuthentication(String username, String password) throws IOException{
			try {
				Document doc = documentbuilderfactory.newDocumentBuilder().newDocument();
				Element root = doc.createElement("message");
				root.setAttribute("type","auth-request");
				doc.appendChild(root);
				Element auth = doc.createElement("authentication");
				auth.setAttribute("username",username);
				auth.setAttribute("password",password);
				root.appendChild(auth);
				this.sendDocument(doc);
			} catch (ParserConfigurationException e) {
				logger.log(Level.SEVERE, "unable to create new document for authentication.", e);
			}
		}

		public boolean receiveAuthenticationResult() throws IOException {
			try {
				Document doc = receiveDocument();
				Element root = doc.getDocumentElement();
				if (root==null) return false;
				if (!root.getAttribute("type").equalsIgnoreCase("auth-response")) 
				    return false;
				
				NodeList nl = root.getChildNodes();
				Element authresult = null;
				for (int i=0;i<nl.getLength();i++) {
					Node n = nl.item(i);
					if (n.getNodeType()==Element.ELEMENT_NODE && n.getNodeName().equalsIgnoreCase("authentication")) {
						authresult = (Element) n;
						break;
					}
				}
				if (authresult.getAttribute("result").equalsIgnoreCase("ok"))
	                return true;
			} catch (Exception e) {
				e.printStackTrace();
			}
	        return false;
		}
		
		public boolean doAuthentication(String username, String password) throws IOException {
			sendAuthentication(username, password);
			return receiveAuthenticationResult();
		}
		
		public byte[] receivePacket() throws IOException, SocketClosedException {
			ByteArrayOutputStream buffer = new ByteArrayOutputStream();
			int read = inputstream.read();
			while (read!=0) {
				if (read==-1) {
					throw new SocketClosedException(); 
				}
				buffer.write(read);
				read = inputstream.read();
			}
			return buffer.toByteArray();
		}
		
		public Document receiveDocument() throws SAXException { // , IOException, ParserConfigurationException, SocketClosedException {
		    try {
	    		byte[] raw = receivePacket();
	    		Document doc = documentbuilderfactory.newDocumentBuilder().parse(new ByteArrayInputStream(raw));
	    		return doc;
	        } catch (SocketClosedException e) {
	            logger.log(Level.SEVERE, "Socket was closed:"+e);
	            connected = false;
	        } catch (SocketException e) {
	            logger.log(Level.SEVERE, "Socket exception:"+e);
	        } catch (Exception e) {
	            logger.log(Level.SEVERE, "ACAgent receiveDocument exception", e);
	        }
			/*
			try {
				if (logger.isLoggable(Level.FINE)) {
					ByteArrayOutputStream temp = new ByteArrayOutputStream();
					transformerfactory.newTransformer().transform(new DOMSource(doc),new StreamResult(temp));
					logger.fine("Received message:\n"+temp.toString());
				}
			} catch (Exception e) {}
			*/
			return null;
		}

		protected boolean connect() {
	    	connected = false;
			try {
				//socketaddress = new InetSocketAddress(networkhost,networkport);
	            socket = new Socket(networkhost,networkport);//socket.connect(socketaddress);
				inputstream  = socket.getInputStream();
				outputstream = socket.getOutputStream();

				if (doAuthentication(username, password)) {
					processLogIn();
					connected = true;
				} else {
					logger.log(Level.SEVERE, "authentication failed");
	            }                   
			} catch (Exception e) {
				logger.log(Level.SEVERE, "Connection exception "+e);
			}			
	        return connected;
	    }
	    
	    public boolean isConnected() {
	    	return connected;
	    }

		public void processSimulationStart(Element simulation, long currenttime) {
			try {
				//opponent = simulation.getAttribute("opponent");
				//arq.addBel(Literal.parseLiteral("opponent("+simulationID+","+opponent+")"));
	            arq.setSimId(simulation.getAttribute("id"));

				int gsizex = Integer.parseInt(simulation.getAttribute("gsizex"));
				int gsizey = Integer.parseInt(simulation.getAttribute("gsizey"));
	            arq.gsizePerceived(gsizex,gsizey, simulation.getAttribute("opponent"));

				int corralx0 = Integer.parseInt(simulation.getAttribute("corralx0"));
	            int corralx1 = Integer.parseInt(simulation.getAttribute("corralx1"));
	            int corraly0 = Integer.parseInt(simulation.getAttribute("corraly0"));
	            int corraly1 = Integer.parseInt(simulation.getAttribute("corraly1"));
	            arq.corralPerceived(new Location(corralx0, corraly0), new Location(corralx1, corraly1));

				int steps  = Integer.parseInt(simulation.getAttribute("steps"));
	            arq.stepsPerceived(steps);
				
				logger.info("Start simulation processed ok!");
				logger.info("X0: " + corralx0 + " Y0: " + corraly0 + " X1: " + corralx1 + " Y1: " + corraly1);
				

				rid = simulation.getAttribute("id");
				
			} catch (Exception e) {
				logger.log(Level.SEVERE, "error processing start",e);
			}
		}

		public void processSimulationEnd(Element result, long currenttime) {
			try {
	            String score = result.getAttribute("score") +"-"+ result.getAttribute("result");
				logger.info("End of simulation :"+score);
	            arq.simulationEndPerceived(result.getAttribute("result"));
			} catch (Exception e) {
				logger.log(Level.SEVERE, "error processing end",e);
			}
		}

	    public String xmlToString(Node node) {
	        try {
	            Source source = new DOMSource(node);
	            StringWriter stringWriter = new StringWriter();
	            Result result = new StreamResult(stringWriter);
	            TransformerFactory factory = TransformerFactory.newInstance();
	            Transformer transformer = factory.newTransformer();
	            transformer.transform(source, result);
	            return stringWriter.getBuffer().toString();
	        } catch (TransformerConfigurationException e) {
	            e.printStackTrace();
	        } catch (TransformerException e) {
	            e.printStackTrace();
	        }
	        return null;
	    }
	    
		public boolean processMessage(Element el_message) {
			String type = el_message.getAttribute("type");
			if (type.equals("request-action") || type.equals("sim-start") || type.equals("sim-end")) {
				long deadline = 0;
				long currenttime = 0;
				try {
					currenttime = Long.parseLong(el_message.getAttribute("timestamp"));
				} catch (NumberFormatException e) {
					logger.log(Level.SEVERE,"number format invalid",e);
					return true;
				}

				// get perception
				Element el_perception = null;
				NodeList nl = el_message.getChildNodes();
				String infoelementname ="perception";
				if (type.equals("request-action")) {
					infoelementname = "perception";
				} else if (type.equals("sim-start")) {
					infoelementname = "simulation";
				} else if (type.equals("sim-end")) {
					infoelementname = "sim-result";
				}

				for (int i=0;i<nl.getLength();i++) {
					Node n = nl.item(i);
					if (n.getNodeType()==Element.ELEMENT_NODE && n.getNodeName().equalsIgnoreCase(infoelementname)) {
						if (el_perception==null) {
							el_perception = (Element) n;
							break;
						}
					}
				}


				if (type.equals("request-action")) {
					try {
						deadline = Long.parseLong(el_perception.getAttribute("deadline"));
					} catch (NumberFormatException e) {
						logger.log(Level.SEVERE,"number format invalid",e);
						return true;
					}
									
					processRequestAction(el_perception, currenttime, deadline);

				} else if (type.equals("sim-start")) {
					processSimulationStart(el_perception, currenttime);

				} else if (type.equals("sim-end")) {
					processSimulationEnd(el_perception, currenttime);
				}
				

			} else if (type.equals("pong")) {
				NodeList nl = el_message.getChildNodes();
				for (int i=0;i<nl.getLength();i++) {
					Node n = nl.item(i);
					if (n.getNodeType()==Element.ELEMENT_NODE && n.getNodeName().equalsIgnoreCase("payload")) {
						processPong(((Element)n).getAttribute("value"));
						return true;
					}
				}
			}
			return true;
		}


		
		public void processRequestAction(Element perception, long currenttime, long deadline) {
			try {
//				logger.info(xmlToString(perception));
				
				List<Literal> percepts = new ArrayList<Literal>();
				
				rid = perception.getAttribute("id");
				int agx   = Integer.parseInt(perception.getAttribute("posx"));
				int agy   = Integer.parseInt(perception.getAttribute("posy"));
				int step  = Integer.parseInt(perception.getAttribute("step"));
	            //int score = Integer.parseInt(perception.getAttribute("score"));

	            // update model
				arq.locationPerceived(agx, agy);
	            //arq.setScore(score);

	            // add location in perception
				Literal lpos = new LiteralImpl("pos");
				lpos.addTerms(new NumberTermImpl(agx), new NumberTermImpl(agy), new NumberTermImpl(step));
				percepts.add(lpos);

//				arq.initKnownCows();
				
				//int enemyId = 1;
				
				// add in perception what is around
				NodeList nl = perception.getElementsByTagName("cell");
				for (int i=0; i < nl.getLength(); i++) {
					Element cell = (Element)nl.item(i);
					int cellx = Integer.parseInt(cell.getAttribute("x"));
	                int celly = Integer.parseInt(cell.getAttribute("y"));
					int absx  = agx + cellx;
					int absy  = agy + celly;
					
					NodeList cnl = cell.getChildNodes();
					for (int j=0; j < cnl.getLength(); j++) {
						if (cnl.item(j).getNodeType() == Element.ELEMENT_NODE && cellx != 0 && celly != 0) {

							Element type = (Element)cnl.item(j);
							if (type.getNodeName().equals("agent")) {
								if (type.getAttribute("type").equals("ally")) {
								    // allies are managed by communication
									//percepts.add(EcompArch.createCellPerception(cellx, celly, CowboyArch.aALLY));
								} else if (type.getAttribute("type").equals("enemy")) {
		                            //Structure le = new Literal("enemy");
		                            //le.addTerm(new NumberTermImpl( (enemyId++) )); // we need an id to work with UniqueBB
									//percepts.add(EcompArch.createCellPerception(cellx, celly, le));
	                                arq.enemyPerceived(absx, absy);
								}
	                            
	                        } else if (type.getNodeName().equals("cow")) {
	                            // ignore cows in the border, they complicate all :-)
//	                            if (absx < arq.getModel().getWidth()-1 && absx != 0 && absy != 00 && absy < arq.getModel().getHeight()-1) {
								int cowId = Integer.parseInt(type.getAttribute("ID"));
//								Literal lc = new LiteralImpl("cow");
//								lc.addTerms(new NumberTermImpl(cowId),
//										new NumberTermImpl(absx),
//										new NumberTermImpl(absy));
//								Structure stepannot = new Structure("step", 1);
//								stepannot.addTerm(new NumberTermImpl(step));
//								lc.addAnnot(stepannot);
//								percepts.add(lc);
								arq.cowPerceived(absx, absy, cowId, EcompArch.createCellPerception(absx, absy, cowId, EcompArch.aCOW));
//	                            }
	                            
	                        } else if (type.getNodeName().equals("obstacle")) { 
								arq.obstaclePerceived(absx, absy, EcompArch.createCellPerception(absx, absy, EcompArch.aOBSTACLE));
	                        } else if (type.getNodeName().equals("corral") && type.getAttribute("type").equals("enemy")) { 
	                            arq.enemyCorralPerceived(absx, absy, EcompArch.createCellPerception(absx, absy, EcompArch.aENEMYCORRAL));
	                            
	                        } else if (type.getNodeName().equals("fence")) {
	                        	Literal fence = new LiteralImpl("fence");
	                        	String status = type.getAttribute("open").equals("true") ? "open" : "closed";
	                			fence.addTerms(new NumberTermImpl(absx), new NumberTermImpl(absy), new Atom(status));
	                			percepts.add(fence);
	                			
	                			if(status.equals("open")) { // remove from world model
	                				arq.removeFence(absx, absy, EcompArch.createCellPerception(absx, absy, EcompArch.aNOFENCE));
	                			} else { // add fence to world model
	                				arq.fencePerceived(absx, absy, EcompArch.createCellPerception(absx, absy, EcompArch.aFENCE));
	                			}
	                			
	                        } else if (type.getNodeName().equals("switch")) {
	                        	Literal switchButton = new LiteralImpl("switch");
	                        	switchButton.addTerms(new Atom("corral/other"), new NumberTermImpl(absx), new NumberTermImpl(absy));
	                        	percepts.add(switchButton);
	                        	
	                        	arq.switchPerceived(absx, absy, EcompArch.createCellPerception(absx, absy, EcompArch.aSWITCH));                        	
//	                      	} else if (type.getNodeName().equals("empty") || type.getNodeName().equals("unknown")) {
//	                      		arq.emptyPerceived(absx,absy, CowboyArch.createCellPerception(absx, absy, CowboyArch.aEMPTY));
	                      	}
						}
					}
					
//					logger.info("Time: " + (System.currentTimeMillis()-start));
				}
		
	            //if (logger.isLoggable(Level.FINE)) 
//	            logger.info("Request action for "+lpos+" / rid: "+rid+" / percepts: "+percepts);            
				
				//arq.sendCowsToTeam();
				arq.startNextStep(step, percepts);
				
			} catch (Exception e) {
				logger.log(Level.SEVERE, "error processing request",e);
			}
		}

		public void sendAction(String action) {
			try {
//				logger.info("sending action "+action+" for rid "+rid+" at "+arq.model.getAgPos(arq.getMyId()) );
				Document doc = documentbuilder.newDocument();
				Element el_response = doc.createElement("message");
				
				el_response.setAttribute("type","action");
				doc.appendChild(el_response);

				Element el_action = doc.createElement("action");
				if (action != null) {
					el_action.setAttribute("type", action);
				}
				el_action.setAttribute("id",rid);
				el_response.appendChild(el_action);

				sendDocument(doc);
			} catch (Exception e) {
				logger.log(Level.SEVERE,"Error sending action.",e);
			}
		}

		public void processPong(String pong) {
			logger.info("---#-#-#-#-#-#-- processPong("+pong+") --#-#-#-#-#-#---");
			monitor.processPong(pong);
		}

		/********************************************************************************************************************/
		
		/** checks the connection */
		class ConnectionMonitor extends Thread {
			long    sentTime = 0;
			int     count = 0;
			boolean ok = true;
			
			public void run() {
				int d = new Random().nextInt(10000);
	            try {
	                while (running) {
	                    if (isConnected())
	                        sleep(20000+d);
	                    else 
	                        sleep(2000);
						count++;
						ok = false;
						sentTime = System.currentTimeMillis();
						if (isConnected()) {
							sendPing("test:"+count);
							waitPong();
						}
						if (!ok) {
							logger.info("I likely loose my connection, reconnecting!");
							//reconnect();
							connect();
						}
				    }
	            } catch (InterruptedException e) {
	            } catch (Exception e) {
	                logger.log(Level.WARNING,"Error in communication ",e);
	            }
			}
			
			synchronized void waitPong() throws Exception {
				wait(4000);
			}
			
			synchronized void processPong(String pong) {
				long time = System.currentTimeMillis() - sentTime;
				logger.info("Pong "+pong+" in "+time+" milisec");
				ok = true;
				notify();
			}
		}
		
		public void processLogIn() {
			logger.info("---#-#-#-#-#-#-- login --#-#-#-#-#-#---");
		}

		Object syncSend = new Object();
		
		public void sendDocument(Document doc)  {
			try {
				ByteArrayOutputStream temp = new ByteArrayOutputStream();
				transformerfactory.newTransformer().transform(new DOMSource(doc),new StreamResult(temp));
				if (logger.isLoggable(Level.FINE)) logger.fine("Sending:"+temp.toString());
				synchronized (syncSend) {
					transformerfactory.newTransformer().transform(new DOMSource(doc),new StreamResult(outputstream));
					outputstream.write(0);
					outputstream.flush();
				}
			} catch (TransformerConfigurationException e) {
				logger.log(Level.SEVERE, "transformer config error" ,e);
			} catch (TransformerException e) {
				logger.log(Level.SEVERE,"transformer error error",e);
			} catch (IOException e) {
				logger.log(Level.SEVERE,"error in sendDocument -- reconnect",e);
				connect();
			}
		}
		
		public void sendPing(String ping) throws IOException {
			Document doc = null;
			try {
				doc = documentbuilderfactory.newDocumentBuilder().newDocument();
			} catch (ParserConfigurationException e) {
				logger.log(Level.SEVERE,"parser config error",e);
				return;
			}
			try {
				Element root = doc.createElement("message");
				doc.appendChild(root);
				root.setAttribute("type","ping");
				Element payload = doc.createElement("payload");
				payload.setAttribute("value",ping);
				root.appendChild(payload);
				sendDocument(doc);
			} catch (Exception e) {
				logger.log(Level.SEVERE,"error seding ping "+e);			
			}
		}
	}
	
	class AgentActionThread extends Thread {

	    ActionExec        lastAction;
        String            lastActionInCurrentCycle;
	    Queue<ActionExec> toExecute = new ConcurrentLinkedQueue<ActionExec>();
	    Lock              lock = new ReentrantLock();
	    Condition         cycle = lock.newCondition();
	    long              timestartcycle = 0;
	    long              timeLastAction = 0;
	    int               cycleCounter = 0;
	    
	    ServerProxyThread proxy;
	    
	    AgentActionThread(ServerProxyThread proxy) {
	        super("AgentActionThread");
	        
	        this.proxy = proxy;
	    }
	    
	    void addAction(ActionExec action) {
	    	lock.lock();
            try {
    	        if (lastAction != null)
    	            toExecute.offer(lastAction);
    	        lastAction = action;
            } finally {
            	lock.unlock();
            }
        }
	    
	    void newCycle() {
	    	cycleCounter++;
	    	if (getSimStep() == 1) cycleCounter = 1;
	    	
            StringBuilder notsent = new StringBuilder();
            if (toExecute.size() > 1) {
            	notsent.append(" The following was not sent: ");
            }
            // set all actions as successfully executed
            List<ActionExec> feedback = getTS().getC().getFeedbackActions();
            synchronized (feedback) {
                while (!toExecute.isEmpty()) {
                    ActionExec action = toExecute.poll();
                    action.setResult(true);
                    feedback.add(action);
                    if (!toExecute.isEmpty())
                        notsent.append(action.getActionTerm()+" ");
                }                
            }
            go(); // reset the wait
            
            // prepare msg to print out
	        String w = "";
	        if (lastActionInCurrentCycle == null && cycleCounter > 10) { // ignore problem in the first cycles (the agent is still in setup!)
	        	addRestart();
	        	w = "*** ";
	        }
	        
	        String timetoact = ". ";
	        if (lastActionInCurrentCycle != null && timestartcycle > 0) {
	        	timetoact = " (act in "+ (timeLastAction -  timestartcycle) +" ms)";
	        }
	        timestartcycle = System.currentTimeMillis();

//            logger.info(w+"Last sent action was "+lastActionInCurrentCycle+" for cycle "+getSimStep()+ timetoact + notsent);            
            setLastAct(lastActionInCurrentCycle);
            lastActionInCurrentCycle = null;
	    }
	    
    	void go() {
	    	lock.lock();
            try {
            	cycle.signal();
            } finally {
            	lock.unlock();
            }
        }
	    
	    boolean waitSleep() throws InterruptedException {
	    	lock.lock();
            try {
            	return !cycle.await(actionTimeout, TimeUnit.MILLISECONDS);
            } finally {
            	lock.unlock();
            }
        }
	    
	    @Override
	    public void run() {
	        while (true) {
	            try {
                    lastAction = null;
                    waitSleep();

	                if (lastAction != null) {
                        lastActionInCurrentCycle = lastAction.getActionTerm().getTerm(0).toString();
	                    proxy.sendAction(lastActionInCurrentCycle);
	                    toExecute.offer(lastAction);
	                    timeLastAction = System.currentTimeMillis();
	                }
	            } catch (InterruptedException e) {
	                return; // condition to stop the thread 
	            } catch (Exception e) {
	                toExecute.clear();
	            }
	        }
	    }
	}
	
    public static Literal createCellPerception(int x, int y, Term obj) {
        Literal l = new Pred("cell");
        l.addTerms(new NumberTermImpl(x),
                   new NumberTermImpl(y),
                   obj); 
        return l;
    }
    
    public static Literal createCellPerception(int x, int y, int id, Term obj) {
        Literal l = new Pred("cell");
        l.addTerms(new NumberTermImpl(x),
                   new NumberTermImpl(y),
                   new NumberTermImpl(id),
                   obj); 
        return l;
    }

	protected void addRestart() {
    	try {
        	logger.info("** Arch adding restart for "+getAgName());
    	    //getTS().getC().create(); // it is terrible for pending intentions of cowboys!
        	getTS().getC().addAchvGoal(new LiteralImpl("restart"), Intention.EmptyInt);
        	lo2 = new Location(-1,-1); // to not restart again in the next cycle
    	} catch (Exception e) {
        	logger.info("Error in restart!"+ e);
    	}
	}

	@Override
	public boolean isCrashed() {
	    return playing && super.isCrashed();
	}

	void setSimId(String id) {
        simId = id;
    }
    
	public int getMyId() {
		if (myId < 0) {
			myId = getAgId(getAgName());
		}
		return myId;
	}

    public static int getAgId(String agName) {
		return (Integer.parseInt(
				agName.replace("ag", ""))) - 1;    	
    }
    
	protected String lastAct;
	protected void setLastAct(String act) {
	    lastAct = act;
	}
	public String getLastAction() {
	    return lastAct;
	}
	
	public int getSimStep() {
	    return simStep;
	}
	
	void gsizePerceived(int w, int h, String opponent) throws RevisionFailedException {
        Literal gsizeLiteral = Literal.parseLiteral(
				"gsize("+w+","+h+")");
        getTS().getAg().addBel(gsizeLiteral);
        playing = true;
	}
	
    public void startNextStep(int step, List<Literal> percepts) {
		// TODO Auto-generated method stub
		
	}

	public void switchPerceived(int absx, int absy, Literal createCellPerception) {
		// TODO Auto-generated method stub
		
	}

	public void fencePerceived(int absx, int absy, Literal createCellPerception) {
		// TODO Auto-generated method stub
		
	}

	public void removeFence(int absx, int absy, Literal createCellPerception) {
		// TODO Auto-generated method stub
		
	}

	public void enemyCorralPerceived(int absx, int absy,
			Literal createCellPerception) {
		// TODO Auto-generated method stub
		
	}

	public void obstaclePerceived(int absx, int absy,
			Literal createCellPerception) {
		// TODO Auto-generated method stub
		
	}

	public void cowPerceived(int absx, int absy, int cowId,
			Literal createCellPerception) {
		// TODO Auto-generated method stub
		
	}

	public void enemyPerceived(int absx, int absy) {
		// TODO Auto-generated method stub
		
	}

	public void locationPerceived(int agx, int agy) {
		// TODO Auto-generated method stub
		
	}

	public void simulationEndPerceived(String attribute) {
		// TODO Auto-generated method stub
		
	}

	public void stepsPerceived(int steps) {
		// TODO Auto-generated method stub
		
	}

	public void corralPerceived(Location location, Location location2) {
		// TODO Auto-generated method stub
		
	}

	//...
	
	@Override
	public void checkMail() {
		super.checkMail();
		
//		Iterator<Message> im =
//			getTS().getC().getMailBox().iterator();
//		while (im.hasNext()) {
//			Message m  = im.next();
//		}
	}
	
	@Override
	public void broadcast(Message m) throws Exception {
		String basename = getAgName().replaceAll("[0-9]*$", "");
    	for (int i=0; i < 20; i++) {
    		if (i == getMyId()) continue;

			String dstName = basename + (i + 1);
			Message msg = new Message(m);
			msg.setReceiver(dstName);
			for (int t = 0; t < 10; t++) {
				try {
					sendMsg(msg);
					break; // the for
				} catch (ReceiverNotFoundException e) {
					// wait and try again
					Thread.sleep(500);
				}
			}
		}
	}

    public static final int   actionTimeout = 1500; // timeout to send an action

    
}
